package com.yanqu.road.logcollector.log;

import com.yanqu.road.dao.db.*;
import com.yanqu.road.logic.model.ck.CkDbData;
import com.yanqu.road.logic.model.ck.CkLogParam;
import com.yanqu.road.utils.JsonUtils;
import com.yanqu.road.utils.date.DateHelper;
import com.yanqu.road.utils.executor.ScheduledThreadPoolHelper;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.sql.Connection;
import java.sql.Types;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 日志收集
 */
public class CKLogMgr {

    private static Logger logger = LogManager.getLogger(CKLogMgr.class);

    private static CKLogMgr instance = new CKLogMgr();

    public static CKLogMgr getInstance() {
        return instance;
    }

    /**
     * 保存阈值
     */
    private static int saveThreshold = 100000;

    /**
     * 待保存的日志
     */
    private static Map<String, CkLogParam> ckLogParamMap = new ConcurrentHashMap<>();

    private static long lastSaveTime = System.currentTimeMillis();

    /**
     * 需要保存的日志数量
     */
    private static volatile int countNum = 0;

    /**
     * 是否可以写入
     */
    private static boolean canSave = false;

    private static ScheduledThreadPoolExecutor logThreadPoolExecutor = ScheduledThreadPoolHelper.createGameScheduledThreadPoolExecutor(8, "log");

    static {
        logThreadPoolExecutor.scheduleAtFixedRate(() -> {
            getInstance().save(false);
        }, 0, 1000, TimeUnit.MILLISECONDS);
    }

    /**
     * 添加需要保存 日志对象
     */
    public static void addCKLog(String className, String sql, List<Map<Integer, CkDbData>> dataList) {
        synchronized (ckLogParamMap) {
            CkLogParam ckLogParam = ckLogParamMap.get(className);
            if (ckLogParam == null) {
                ckLogParam = new CkLogParam();
                ckLogParam.setClassName(className);
                ckLogParam.setSql(sql);
                ckLogParam.setDataList(dataList);
                ckLogParamMap.put(className, ckLogParam);
            } else {
                ckLogParam.getDataList().addAll(dataList);
            }
            countNum += dataList.size();
            if (countNum > saveThreshold) {
                save(true);
            }
        }
    }

    /**
     * 保存 日志对象
     */
    public static void save(boolean force) {
        if (!force && System.currentTimeMillis() - lastSaveTime < 60 * 1000) {
            if (ckLogParamMap.size() == 0 || !canSave || countNum < saveThreshold) {
                return;
            }
        }
        try {
            long startTime = System.currentTimeMillis();
            Map<String, CkLogParam> tempSaveLogListMap = null;
            int needSaveCountNum = 0;
            int saveCountNum = 0;
            Map<String, Integer> saveMap = new HashMap<>();
            synchronized (ckLogParamMap) {
                tempSaveLogListMap = new HashMap<>(ckLogParamMap);
                ckLogParamMap.clear();
            }
            countNum = 0;
            Connection connection = DbPoolMgr.getInstance().getConn();
            for (Map.Entry<String, CkLogParam> entry : tempSaveLogListMap.entrySet()) {
                long saveStartTime = System.currentTimeMillis();
                String sql = entry.getValue().getSql();
                // 转换一下数据
                List<Map<Integer, DbParameter>> dbParameterList = new ArrayList<>();
                try {
                    dbParameterList = parseDbParameterList(entry.getValue().getDataList());
                } catch (Throwable t) {
                    logger.error("parseDbParameterList 转换异常,className : {}, sql :{}", entry.getKey(), sql);
                }
                if (dbParameterList == null || dbParameterList.isEmpty()) {
                    continue;
                }
                needSaveCountNum += dbParameterList.size();
                try {
                    if (DbPoolMgr.getInstance().executeBatchQuery(connection, sql, dbParameterList) > -1) {
                        saveCountNum += dbParameterList.size();
                        saveMap.put(entry.getKey(), dbParameterList.size());
                    } else {
                        // TODO 加个容错 把丢失的日志写入到文件里去
                        logger.error("保存日志错误 className : {}, sql :{}", entry.getKey(), sql);
                    }
                } catch (Throwable t) {
                    logger.error("入库失败", t);
                }
                long saveUseTime = System.currentTimeMillis() - saveStartTime;
                if (saveUseTime > 10000) {
                    logger.warn("保存日志错误超时.时间为：{}ms,数据个数：{}， className = {}", saveUseTime, dbParameterList.size(), entry.getKey());
                }
            }
            try {
                connection.close();
            } catch (Throwable t) {
                logger.error("关闭连接异常", t);
            }
            long allUseTime = System.currentTimeMillis() - startTime;
            logger.info("CKLogMgr save 统计.时间为：{}ms,需要保存数据个数：{}，实际保存数据个数：{}, detail:{} ", allUseTime, needSaveCountNum, saveCountNum, JsonUtils.toJSONString(saveMap));
        } catch (Throwable t) {
            logger.error("日志保存失败", t);
        }
        lastSaveTime = System.currentTimeMillis();
//        logger.info("CKLogMgr save end");
    }

    private static List<Map<Integer, DbParameter>> parseDbParameterList(List<Map<Integer, CkDbData>> dataList) {
        if (dataList != null && !dataList.isEmpty()) {
            List<Map<Integer, DbParameter>> dbParameterList = new ArrayList<>();
            for (Map<Integer, CkDbData> dataMap : dataList) {
                Map<Integer, DbParameter> dbParameterMap = new HashMap<>();
                for (Map.Entry<Integer, CkDbData> entry : dataMap.entrySet()) {
                    Object sqlValue = entry.getValue().getSqlValue();
                    // 日期转一下
                    if (entry.getValue().getSqlType() == Types.TIMESTAMP) {
                        if (sqlValue instanceof Long) {
                            sqlValue = DateHelper.getCurrentDateTimeString((Long) sqlValue);
                        } else if (sqlValue instanceof Integer) {
                            sqlValue = DateHelper.getCurrentDateTimeString(((Integer) sqlValue).longValue());
                        }
                    }
                    dbParameterMap.put(entry.getKey(), new DbParameter(entry.getValue().getSqlType(), sqlValue, false));
                }
                dbParameterList.add(dbParameterMap);
            }
            return dbParameterList;
        }
        return null;
    }

    public void stop() {
        logger.info("CKLogMgr stop start");
        save(true);
        canSave = false;
        logger.info("CKLogMgr stop end");
    }


    public static void main(String[] args) throws Exception {

    }

    public void setCanSave(boolean canSave) {
        this.canSave = canSave;
    }
}
